package ar.edu.itba.pod.legajo45002;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import ar.edu.itba.balance.api.AgentsBalancer;
import ar.edu.itba.balance.api.NodeAgent;
import ar.edu.itba.balance.api.NotCoordinatorException;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.pod.agent.runner.Agent;

@SuppressWarnings("serial")
public class ClusterAdministratorImpl extends UnicastRemoteObject implements ClusterAdministration {

	private NodeInformation nodeinfo;

	public ClusterAdministratorImpl(NodeInformation info) throws RemoteException {
		this.nodeinfo = info;
		System.out.println(nodeinfo);
		DistributedNodeImpl.allNodes().put(nodeinfo.id(), nodeinfo);
		try {
			System.out.println(DistributedNodeImpl.getMynodeInfo());
			Registry registry = LocateRegistry.createRegistry(DistributedNodeImpl.getMynodeInfo().port());
			registry.rebind(Node.CLUSTER_COMUNICATION, this);
			DistributedNodeImpl.setRegistry(registry);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void createGroup() throws RemoteException {
		DistributedNodeImpl.setCoordinator(nodeinfo);
	}

	@Override
	public String getGroupId() throws RemoteException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isConnectedToGroup() throws RemoteException {
		return  DistributedNodeImpl.allNodes().size() > 1;
	}

	@Override
	public void connectToGroup(String host, int port) throws RemoteException, NotBoundException {
		try {
			System.out.println("Connecting to node: " + host);
			Registry registry = LocateRegistry.getRegistry(host,port);
			ClusterAdministration stub = (ClusterAdministration)registry.lookup(Node.CLUSTER_COMUNICATION);
			Set<NodeInformation>returnedNodes =  stub.addNewNode(nodeinfo);
			if( returnedNodes != null){
				Iterator<NodeInformation> it = returnedNodes.iterator();

				while (it.hasNext()){
					NodeInformation n = it.next();
					if(! DistributedNodeImpl.allNodes().containsKey(n.id())){
						DistributedNodeImpl.allNodes().put(n.id(), n);
						System.out.println("i am at node "+ nodeinfo.id()+" and received nodes:"+ n.id());
					}
				}
			}
		} catch (Exception e) {
			System.out.println("Could not connect to node: " + host);
		//	e.printStackTrace();
		}	

		AgentsBalancer balance = DistributedNodeImpl.getBalancer();
		balance.bullyElection(DistributedNodeImpl.getMynodeInfo(), System.currentTimeMillis());
	}


	public void shutDownAgentsOfNode(NodeInformation node) throws RemoteException, NotBoundException{
		Registry coordReg = LocateRegistry.getRegistry(DistributedNodeImpl.getCoordinator().host(),DistributedNodeImpl.getCoordinator().port());
		AgentsBalancer stub = (AgentsBalancer)coordReg.lookup(Node.AGENTS_BALANCER);
		List<NodeAgent>nodeagents = new LinkedList<NodeAgent>();
		for( Agent a : DistributedNodeImpl.getSimulation().getAgentsRunning()){
			nodeagents.add(new NodeAgent(DistributedNodeImpl.getMynodeInfo(), a));
		}
		boolean sent = false;
		int i = 0;
		while(!sent && i < 3 ){

			try {
				System.out.println("Shutting down agents...");
				stub.shutdown(nodeagents);
				sent = true;

			} catch (NotCoordinatorException e) {
				coordReg = LocateRegistry.getRegistry( e.getNewCoordinator().host(),e.getNewCoordinator().port());
				stub = (AgentsBalancer)coordReg.lookup(Node.CLUSTER_COMUNICATION);
				//	e.printStackTrace();
				i++;
			}
		}
	}

	@Override
	public void disconnectFromGroup(NodeInformation node) throws RemoteException, NotBoundException {
		System.out.println("my nodes list: "+ DistributedNodeImpl.allNodes().values());
		if( DistributedNodeImpl.allNodes().containsKey(node.id())){
			System.out.println("Disconnecting node: "+ node);
			DistributedNodeImpl.allNodes().remove(node.id());

			for( NodeInformation aNode : DistributedNodeImpl.allNodes().values()){

				//	if( !aNode.equals(DistributedNodeImpl.getMynodeInfo())){

				System.out.println("Sending disconnect to node: " + aNode.id());
				Registry registry = LocateRegistry.getRegistry(aNode.host(),aNode.port());
				ClusterAdministration stub = (ClusterAdministration)registry.lookup(Node.CLUSTER_COMUNICATION);
				stub.disconnectFromGroup(node);
				//		}
			}
			/*
			if( node.equals(DistributedNodeImpl.getMynodeInfo())){
				System.out.println("I'm disconnecting from the cluster");
				//	DistributedNodeImpl.disconnectMe();
				DistributedNodeImpl.allNodes().remove(node.id());
				shutDownAgentsOfNode(node);
			}
			 */
			if( node.equals(DistributedNodeImpl.getCoordinator())){
				DistributedNodeImpl.getBalancer().bullyElection(DistributedNodeImpl.getMynodeInfo(), System.nanoTime());
			}

		}



	}

	@Override
	public Set<NodeInformation> addNewNode(NodeInformation node)throws RemoteException, NotBoundException {
		Set<NodeInformation>nodesSet = new HashSet<NodeInformation>();;
		System.out.println("somebody called me add new node");
		if(! DistributedNodeImpl.allNodes().containsKey(node.id())){
			DistributedNodeImpl.allNodes().put(node.id(), node);
			/*
			if( DistributedNodeImpl.getMynodeInfo().equals(DistributedNodeImpl.getCoordinator()) ){
				DistributedNodeImpl.balanceNewNode(node);
			}
			 */
			for( NodeInformation currentNode : DistributedNodeImpl.allNodes().values()){
				if( currentNode.id() != nodeinfo.id() && currentNode.id() != node.id()){
					System.out.println("callind addNewNode to node: "+ currentNode.id());
					Registry registry = LocateRegistry.getRegistry(currentNode.host(),currentNode.port());
					ClusterAdministration stub = (ClusterAdministration)registry.lookup(Node.CLUSTER_COMUNICATION);
					Set<NodeInformation> nodesT = new HashSet<NodeInformation>();
					nodesT.addAll(stub.addNewNode(node));
					System.out.println("My nodes list");

					for( NodeInformation n:  DistributedNodeImpl.allNodes().values()){
						System.out.println(n.id());
					}
				}
			}

			nodesSet.addAll(DistributedNodeImpl.allNodes().values());

		}
		return nodesSet;
	}

	@Override
	public Set<NodeInformation> connectedNodes() throws RemoteException {
		Set<NodeInformation>nodesSet = new HashSet<NodeInformation>();
		nodesSet.addAll(DistributedNodeImpl.allNodes().values());
		return nodesSet;
	}

}
